﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the collection that can track changes.
    /// </summary>
    /// <typeparam name="T">The type of object.</typeparam>
#if WPF
    [Serializable]
#endif
    [DebuggerDisplay("Count = {Count}")]
    [DataContract(IsReference = true)]
    public class TrackingCollection<T> : ITrackingCollection<T>, ITrackingCollection where T : class
    {
        #region Fields

        private readonly object _locker;
        private IStateTransitionProvider _stateTransitionProvider;
        /// <summary>
        /// Gets the internal dictionary with entity and their states.
        /// </summary>
        protected readonly Dictionary<T, EntityState> Items;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="TrackingCollection{T}" /> class.
        /// </summary>
        public TrackingCollection(IStateTransitionProvider stateTransitionProvider = null,
            IEqualityComparer<T> comparer = null)
        {
            if (stateTransitionProvider == null)
                stateTransitionProvider = new DefaultStateTransitionProvider();
            if (comparer == null)
                comparer = EqualityComparer<T>.Default;
            _locker = new object();

            StateTransitionProvider = stateTransitionProvider;
            Items = new Dictionary<T, EntityState>(comparer);
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="TrackingCollection{T}" /> class.
        /// </summary>
        public TrackingCollection([NotNull]IEnumerable<T> collection, EntityState entityState = EntityState.Unchanged,
            IStateTransitionProvider stateTransitionProvider = null, IEqualityComparer<T> comparer = null)
            : this(stateTransitionProvider, comparer)
        {
            Should.NotBeNull(collection, "collection");
            foreach (T item in collection)
                Items.Add(item, entityState);
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="TrackingCollection{T}" /> class.
        /// </summary>
        public TrackingCollection([NotNull]IEnumerable<KeyValuePair<T, EntityState>> collection,
            IStateTransitionProvider stateTransitionProvider = null, IEqualityComparer<T> comparer = null)
            : this(stateTransitionProvider, comparer)
        {
            Should.NotBeNull(collection, "collection");
            foreach (var item in collection)
                Items.Add(item.Key, item.Value);
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Updates the state of value.
        /// </summary>
        /// <param name="value">The specified value.</param>
        /// <param name="state">The specified state.</param>
        /// <param name="validate">The flag indicating that state will be validated before assigned.</param>
        protected virtual void UpdateStateInternal(T value, EntityState state, bool validate)
        {
            if (value == null)
                return;
            EntityState entityState;
            if (!Items.TryGetValue(value, out entityState))
            {
                state = StateTransitionProvider.ChangeState(EntityState.Detached, state, validate);
                if (!state.IsDetached())
                    Items[value] = state;
                return;
            }
            state = StateTransitionProvider.ChangeState(entityState, state, validate);
            if (state.IsDetached())
                Items.Remove(value);
            else
                Items[value] = state;
        }

        #endregion

        #region Implementation of IEnumerable

        /// <summary>
        ///     Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        ///     A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            using (DeadLockDetector.Lock(_locker))
                return Items.Keys.GetEnumerator();
        }

        /// <summary>
        ///     Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        ///     An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Implementation of ITrackingCollection

        /// <summary>
        ///     Gets a state of value.
        /// </summary>
        /// <param name="value">The specified value.</param>
        /// <returns>
        ///     An instance of <see cref="EntityState" />, if any.
        /// </returns>
        EntityState ITrackingCollection.GetState(object value)
        {
            if (SyncronizedNotifiableCollection<T>.IsCompatibleObject(value))
                return GetState((T)value);
            return EntityState.Detached;
        }

        /// <summary>
        ///     Gets a state of value.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="EntityState" />, if any.
        /// </returns>
        public EntityState GetState<TEntity>(Predicate<TEntity> predicate)
        {
            Should.NotBeNull(predicate, "predicate");
            KeyValuePair<T, EntityState> item;
            using (DeadLockDetector.Lock(_locker))
            {
                item = Items.SingleOrDefault(pair => pair.Key is TEntity && predicate((TEntity)(object)pair.Key));
            }
            if (item.Key == null)
                return EntityState.Detached;
            return item.Value;
        }

        /// <summary>
        ///     Determines whether the <see cref="ITrackingCollection" /> contains a specific value.
        /// </summary>
        bool ITrackingCollection.Contains(object item)
        {
            if (SyncronizedNotifiableCollection<T>.IsCompatibleObject(item))
                return Contains((T)item);
            return false;
        }

        /// <summary>
        ///     Gets an array of all objects with specified entity state.
        /// </summary>
        /// <returns>
        ///     An array of objects.
        /// </returns>
        public IList<TType> Find<TType>(Predicate<KeyValuePair<TType, EntityState>> predicate)
        {
            Should.NotBeNull(predicate, "predicate");
            using (DeadLockDetector.Lock(_locker))
            {
                var list = new List<TType>();
                foreach (var keyValuePair in Items)
                {
                    if (!(keyValuePair.Key is TType)) continue;
                    var item = (TType)(object)keyValuePair.Key;
                    if (!predicate(new KeyValuePair<TType, EntityState>(item, keyValuePair.Value))) continue;
                    list.Add(item);
                }
                return list;
            }
        }

        /// <summary>
        ///     Gets the changes of objects.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IList{T}" />.
        /// </returns>
        IList<IEntityStateEntry> ITrackingCollection.GetChanges()
        {
            return GetChanges()
                .OfType<IEntityStateEntry>()
                .ToArray();
        }

        /// <summary>
        ///     Gets the changes of objects.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IList{T}" />.
        /// </returns>
        IList<IEntityStateEntry> ITrackingCollection.GetChanges(EntityState entityState)
        {
            return GetChanges(entityState)
                .OfType<IEntityStateEntry>()
                .ToArray();
        }

        /// <summary>
        ///     Updates a state in the specified value.
        /// </summary>
        /// <param name="value">The specified value to update state.</param>
        /// <param name="state">The state value.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        /// <returns>
        ///     If <c>true</c> state was changed; otherwise <c>false</c>.
        /// </returns>
        bool ITrackingCollection.UpdateState(object value, EntityState state, bool validateState)
        {
            return UpdateState((T)value, state, validateState);
        }

        /// <summary>
        ///     Removes all items from the <see cref="ITrackingCollection" />.
        /// </summary>
        public void Clear()
        {
            using (DeadLockDetector.Lock(_locker))
            {
                Items.Clear();
            }
        }

        /// <summary>
        ///     Clones the structure of the <see cref="ITrackingCollection" />.
        /// </summary>
        /// <returns>
        ///     A new <see cref="ITrackingCollection" /> with the same schema as the current <see cref="ITrackingCollection" />.
        /// </returns>
        public ITrackingCollection Clone()
        {
            using (DeadLockDetector.Lock(_locker))
                return new TrackingCollection<T>(Items, StateTransitionProvider);
        }

        #endregion

        #region Implementation of ITrackingCollection<T>

        /// <summary>
        ///     Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </summary>
        /// <returns>
        ///     The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.
        /// </returns>
        public int Count
        {
            get
            {
                using (DeadLockDetector.Lock(_locker))
                    return Items.Count;
            }
        }

        /// <summary>
        ///     Gets or sets the <see cref="IStateTransitionProvider" />.
        /// </summary>
        public IStateTransitionProvider StateTransitionProvider
        {
            get { return _stateTransitionProvider; }
            set
            {
                Should.PropertyBeNotNull(value, "StateTransitionProvider");
                _stateTransitionProvider = value;
            }
        }

        /// <summary>
        ///     Gets a value indicating whether the collection has changes, including new, deleted, or modified values.
        /// </summary>
        public bool HasChanges
        {
            get
            {
                using (DeadLockDetector.Lock(_locker))
                    return Items.Values.Any(state => !state.IsUnchanged());
            }
        }

        /// <summary>
        ///     Determines whether the <see cref="ITrackingCollection{T}" /> contains a specific value.
        /// </summary>
        public bool Contains(T item)
        {
            Should.NotBeNull(item, "item");
            using (DeadLockDetector.Lock(_locker))
                return Items.ContainsKey(item);
        }

        /// <summary>
        ///     Gets a state of value.
        /// </summary>
        /// <param name="value">The specified value.</param>
        /// <returns>
        ///     An instance of <see cref="EntityState" />, if any.
        /// </returns>
        public EntityState GetState(T value)
        {
            Should.NotBeNull(value, "value");
            using (DeadLockDetector.Lock(_locker))
            {
                EntityState result;
                if (Items.TryGetValue(value, out result))
                    return result;
                return EntityState.Detached;
            }
        }

        /// <summary>
        ///     Gets a state of value.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="EntityState" />, if any.
        /// </returns>
        public EntityState GetState(Predicate<T> predicate)
        {
            return GetState<T>(predicate);
        }

        /// <summary>
        ///     Gets an array of all objects with specified entity state.
        /// </summary>
        /// <returns>
        ///     An array of objects.
        /// </returns>
        public IList<T> Find(Predicate<KeyValuePair<T, EntityState>> predicate)
        {
            Should.NotBeNull(predicate, "predicate");
            using (DeadLockDetector.Lock(_locker))
            {
                return Items
                    .Where(arg => predicate(arg))
                    .Select(pair => pair.Key)
                    .ToArray();
            }
        }

        /// <summary>
        ///     Gets the changes of objects.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IList{T}" />.
        /// </returns>
        public IList<IEntityStateEntry<T>> GetChanges()
        {
            using (DeadLockDetector.Lock(_locker))
                return Items.Select(pair => EntityStateEntry.Create(pair.Value, pair.Key))
                    .ToArray();
        }

        /// <summary>
        ///     Gets the changes of objects.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IList{T}" />.
        /// </returns>
        public IList<IEntityStateEntry<T>> GetChanges(EntityState entityState)
        {
            using (DeadLockDetector.Lock(_locker))
                return Items.Where(pair => pair.Value == entityState)
                    .Select(pair => EntityStateEntry.Create(pair.Value, pair.Key))
                    .ToArray();
        }

        /// <summary>
        ///     Updates a state in the specified value.
        /// </summary>
        /// <param name="value">The specified value to update state.</param>
        /// <param name="state">The state value.</param>
        /// <param name="validateState">The flag indicating that state will be validated before assigned.</param>
        /// <returns>
        ///     If <c>true</c> state was changed; otherwise <c>false</c>.
        /// </returns>
        public bool UpdateState(T value, EntityState state, bool validateState = false)
        {
            Should.NotBeNull(value, "value");
            using (DeadLockDetector.Lock(_locker))
            {
                UpdateStateInternal(value, state, validateState);
                return true;
            }
        }

        #endregion
    }

    /// <summary>
    ///     Represents the collection that can track changes.
    /// </summary>    
#if WPF
    [Serializable]
#endif
    [DebuggerDisplay("Count = {Count}")]
    [DataContract(IsReference = true)]
    public class TrackingCollection : TrackingCollection<object>
    {
        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="TrackingCollection{T}" /> class.
        /// </summary>
        public TrackingCollection(IStateTransitionProvider stateTransitionProvider = null,
            IEqualityComparer<object> comparer = null)
            : base(stateTransitionProvider, comparer)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="TrackingCollection{T}" /> class.
        /// </summary>
        public TrackingCollection(IEnumerable<object> collection, EntityState entityState = EntityState.Unchanged,
            IStateTransitionProvider stateTransitionProvider = null, IEqualityComparer<object> comparer = null)
            : base(collection, entityState, stateTransitionProvider, comparer)
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="TrackingCollection{T}" /> class.
        /// </summary>
        public TrackingCollection(IEnumerable<KeyValuePair<object, EntityState>> collection,
            IStateTransitionProvider stateTransitionProvider = null, IEqualityComparer<object> comparer = null)
            : base(collection, stateTransitionProvider, comparer)
        {
        }

        #endregion
    }
}